﻿//PulseWave WPF version1.0 with short values
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Windows;

namespace PulseApp
{
    public class PulseWave
    {

        private ArrayList pulseDataArray = new ArrayList();  // store pulse Data
        private ArrayList specialPoints = new ArrayList();       // store special point
        private ArrayList SIs = new ArrayList();                // store SIs
        private ArrayList standardSIs = new ArrayList();        //store SIs for standard Pulse Rate
        static int highRate = 120;                 //the highest heart rate
        static int ratePerSecond = highRate / 60;  //heart rate per second;
        static int pointsPerSecond = 200;          //points per second for device
        static int pointsPerBeat = pointsPerSecond / ratePerSecond; // points per beat for highest rate
        public static int safePoints = pointsPerBeat * 4 / 5;               //safe range for points
        static int standardPulseRate = 70;      // standard Pulse rate
        int standardPoints = pointsPerSecond * 60 / standardPulseRate;  //standard points per pulse
        double safeFromRate = 0.2;                              //safe to find dTop from that point
        double safeToRate = 0.8;                                //safe to find dTop to that point;
        int checkRange = 10;                                //the range to check high and low points
        int height = 0;

        short HR = 0;



        public int age = 0;
        public string description = "";
        public double DTWDistance = 99999999;
        string category = "not categorized";
        public int mostLike = 0;
        public double mostLikeValue = 99999999;


        public PulseWave()
        {
            this.pulseDataArray = new ArrayList();
        }

        public PulseWave(ArrayList inputDataArray, int height) //constructor
        {
            this.height = height;
            this.pulseDataArray = new ArrayList();
            for (int i = 0; i < inputDataArray.Count; i++)
            {
                this.pulseDataArray.Add(short.Parse(inputDataArray[i].ToString()));
            }
            
        }

        public PulseWave(byte[] pulseDataArray, int height)     //constructor with byte[] input, not implement yet
        {
            this.height = height;
            this.pulseDataArray = new ArrayList();
            foreach (byte value in pulseDataArray)
            {
                this.pulseDataArray.Add((short)value);
            }
        }

        public string pulseDataToString()
        {
            string result = "";
            foreach (short value in pulseDataArray)
            {
                result += value + " ";
            }
            return result;
        }

        public byte[] getByteArray()
        {
            byte[] result = null;
            if (this.pulseDataArray != null)
            {
                short[] shortArray = new short[pulseDataArray.Count];
                pulseDataArray.CopyTo(shortArray);
                result = Array.ConvertAll<short,   byte>(shortArray,   Convert.ToByte); 
            }
            return result;
        }

        public short getHR()
        {
            if (HR > 0) return HR;
            if (this.pulseDataArray != null)
            {
                if (this.specialPoints == null) getSIs();
                int bottomCount = 0;
                int firstBottom = 9999;
                int lastBottom = 0;
                foreach (SpecialPoint thePoint in specialPoints)
                {
                    if (thePoint.getType() == SpecialPoint.TYPE_BOTTOM)
                    {
                        bottomCount++;
                        if (thePoint.getPosition() < firstBottom) firstBottom = thePoint.getPosition();
                        if (thePoint.getPosition() > lastBottom) lastBottom = thePoint.getPosition();
                    }
                }
                int temp =(((bottomCount - 1)*(200*60)/(lastBottom-firstBottom)));
                if (temp > 255) temp = 255;
                HR =(short)temp;
            }
            return HR;
        }

        

        //get the position of Last top before the specific position
        //method not be used directly
        private int getLastTop(int position)
        {
            int result = -1;
            if (specialPoints == null || specialPoints.Count == 0)
            {
                this.findPoints();
            }
            foreach (SpecialPoint aPoint in specialPoints)
            {
                if (aPoint.getPosition() > result && aPoint.getPosition() < position && aPoint.getType() == SpecialPoint.TYPE_TOP) result = aPoint.getPosition();
                if (aPoint.getPosition() > position) break;
            }
            return result;
        }

        //get the position of net bottom after the specific position
        //method not be used directly
        private int getNextBottom(int position)
        {
            int result = -1;
            if (specialPoints == null || specialPoints.Count == 0)
            {
                this.findPoints();
            }
            foreach (SpecialPoint aPoint in specialPoints)
            {
                if (aPoint.getPosition() > position && aPoint.getType() == SpecialPoint.TYPE_BOTTOM)
                {
                    result = aPoint.getPosition();
                    break;
                }
            }
            return result;
        }

        //get Special point index for dTop near the position
        //method not be used directly
        private int getDtopSP(int position)
        {
            int result = -1;
            if (specialPoints.Count == 0)
            {
                this.findPoints();
                this.getSIs();
            }
            int lastTop = this.getLastTop(position);
            int nextBottom = this.getNextBottom(position);
            foreach (SpecialPoint aPoint in specialPoints)
            {
                if (aPoint.getType() == SpecialPoint.TYPE_DTOP && aPoint.getPosition() > lastTop && aPoint.getPosition() < nextBottom)
                {
                    result = specialPoints.IndexOf(aPoint);
                    break;
                }
            }
            return result;
        }

        //set a position to be diastolic top
        public void setDtop(int position)
        {
            int index = this.getDtopSP(position);
            if (index > 0)
            {
                ((SpecialPoint)specialPoints[index]).setPosition(position);
            }
            else
            {
                SpecialPoint aPoint = new SpecialPoint(position, SpecialPoint.TYPE_DTOP);
                specialPoints.Insert(index + 1, aPoint);
            }
        }

        private int derivative(ArrayList pulseDataArray, int position)   //find averge derivative of a point
        {
            int result = 0;
            int count = 0;
            int range = 2;
            int start = position - range;
            if (start < 1) start = 1;
            int end = position + range;
            if (end > pulseDataArray.Count - 1) end = pulseDataArray.Count - 1;
            for (int i = start; i < end; i++)
            {
                result += ((short)pulseDataArray[i] - (short)pulseDataArray[i - 1]);
                count++;
            }
            result = result / count;
            return result;
        }

        private float derivativeF(ArrayList pulseDataArray, int position)   //find averge derivative of a point
        {
            float result = 0;
            int count = 0;
            int range = 1;
            int start = position - range;
            if (start < 1) start = 1;
            int end = position + range;
            if (end > pulseDataArray.Count - 1) end = pulseDataArray.Count - 1;
            for (int i = start; i <= end; i++)
            {
                result += ((short)pulseDataArray[i] - (short)pulseDataArray[i - 1]);
                count++;
            }
            result = result * 1F / count;
            return result;
        }

        //return pulse data outside
        public ArrayList getPulseDataArray()
        {
            return this.pulseDataArray;
        }

        //get the data array for the first complete waveform
        public ArrayList getFirstWaveformDataArray()
        {
            ArrayList result = new ArrayList();
            if (this.pulseDataArray == null && this.pulseDataArray.Count == 0)
            {
                //Console.Write("don't have data 1");
                return null;
            }
            else
            {
                if (this.specialPoints == null || this.specialPoints.Count<2)
                {
                    getSIs();
                    Console.WriteLine("specialpoint: " + specialPoints.Count);
                }
                foreach (SpecialPoint sp in specialPoints)
                {
                    if (sp.getType() == SpecialPoint.TYPE_BOTTOM)
                    {
                        Console.Write("Find Bottom 1: " + sp.getPosition());
                        int start = sp.getPosition();
                        int end = getNextBottom(start + 1);
                        Console.Write("Find Bottom 1: " + end);
                        for (int i = start; i <= end; i++)
                        {
                            result.Add(this.pulseDataArray[i]);
                        }
                        Console.Write("have data");
                        return result;
                    }
                }
            }
            //Console.Write("don't have data2");
            return null;
        }

        //return specialpoints
        public ArrayList getSpecialPoints()
        {
            return this.specialPoints;
        }

        //Convert the pulse wave data to an Array of points
        public Point[] getPoints(int baseLine)
        {
            if (this.pulseDataArray == null) { return null; }
            else
            {
                Point[] result = new Point[pulseDataArray.Count];
                for (int i = 0; i < pulseDataArray.Count; i++)
                {
                    Point aPoint = new Point(i, baseLine - (short)pulseDataArray[i]);
                    result[i] = aPoint;
                }
                return result;
            }
        }

        //converto the derivitive to an array of points (integer value)
        public Point[] derivitivePoints(int baseLine)
        {
            if (this.pulseDataArray == null) { return null; }
            else
            {
                Point[] result = new Point[pulseDataArray.Count];
                for (int i = 0; i < pulseDataArray.Count; i++)
                {
                    Point aPoint = new Point(i, baseLine - 10 * this.derivative(pulseDataArray, i));
                    result[i] = aPoint;
                }
                return result;
            }
        }

        //convert the derivitive to an array of point (float value)
        /*public PointF[] derivitivePointsF(int baseLine)
        {
            if (this.pulseDataArray == null) { return null; }
            else
            {
                PointF[] result = new PointF[pulseDataArray.Count];
                for (int i = 0; i < pulseDataArray.Count; i++)
                {
                    PointF aPoint = new PointF(i, baseLine - 10 * this.derivativeF(pulseDataArray, i));
                    result[i] = aPoint;
                }
                return result;
            }
        }*/

        //mark special points
        private void markPoints()
        {
            if (this.pulseDataArray != null && this.pulseDataArray.Count > 0)
            {
                int topSum = 0;         //sum of all top points
                int topCount = 0;       //count for top points
                int bottomSum = 0;      //sum of all bottom points
                int bottomCount = 0;    //count for bottom points
                for (int i = checkRange; i < pulseDataArray.Count - checkRange; i++)
                {
                    Boolean hBool = true;   //mark for high
                    Boolean lBool = true;   //mark for low

                    for (int j = i - checkRange; j < i; j++)//check the point whether a high(low) point or not
                    {
                        if ((short)pulseDataArray[j] >= (short)pulseDataArray[i]) hBool = false;
                        if ((short)pulseDataArray[j] <= (short)pulseDataArray[i]) lBool = false;
                    }
                    for (int j = i + 1; j < i + checkRange; j++)
                    {
                        if ((short)pulseDataArray[j] > (short)pulseDataArray[i]) hBool = false;
                        if ((short)pulseDataArray[j] < (short)pulseDataArray[i]) lBool = false;
                    }
                    if (hBool)  //check if the point is a top
                    {
                        SpecialPoint specialPoint = new SpecialPoint(i, SpecialPoint.TYPE_HIGH);
                        if (i > safePoints && i < pulseDataArray.Count - safePoints)
                        {
                            Boolean tBool = true;   //mark for top
                            for (int j = i - safePoints; j < i + safePoints; j++)
                            {
                                if ((short)pulseDataArray[j] > (short)pulseDataArray[i]) tBool = false;
                            }
                            if (tBool)
                            {
                                specialPoint.setType(SpecialPoint.TYPE_TOP);
                                topSum += (short)pulseDataArray[i];
                                topCount++;
                            }
                        }
                        specialPoints.Add(specialPoint);
                    }
                    else if (lBool) //check if the point is a bottom
                    {
                        SpecialPoint specialPoint = new SpecialPoint(i, SpecialPoint.TYPE_LOW);
                        if (i > safePoints && i < pulseDataArray.Count - safePoints)
                        {
                            Boolean bBool = true;
                            for (int j = i - safePoints; j < i + safePoints; j++)
                            {
                                if ((short)pulseDataArray[j] < (short)pulseDataArray[i]) bBool = false;
                            }
                            if (bBool)
                            {
                                specialPoint.setType(SpecialPoint.TYPE_BOTTOM);
                                bottomSum += (short)pulseDataArray[i];
                                bottomCount++;
                            }
                        }
                        specialPoints.Add(specialPoint);
                    }
                }
                int topAvg = topSum / topCount;
                int bottomAvg = bottomSum / bottomCount;
                int difference = topAvg - bottomAvg;
                for (int i = 0; i < specialPoints.Count; i++)  //delete useless points
                {
                    SpecialPoint bPoint = (SpecialPoint)specialPoints[i];
                    if (bPoint.getType() != SpecialPoint.TYPE_BOTTOM)
                    {
                        int value = (short)pulseDataArray[bPoint.getPosition()];
                        if (bPoint.getType() == SpecialPoint.TYPE_LOW && i > 0 && i < specialPoints.Count - 1 &&
                            ((SpecialPoint)specialPoints[i - 1]).getType() == SpecialPoint.TYPE_TOP &&
                            ((SpecialPoint)specialPoints[i + 1]).getType() == SpecialPoint.TYPE_HIGH) { }
                        else if (value - bottomAvg < difference / 4) specialPoints.RemoveAt(i);
                    }
                }
            }
        }

        //find dtop by calculate derivitives from top to bottom
        private SpecialPoint findDtopByDerivitive(SpecialPoint top, SpecialPoint bottom)
        {
            SpecialPoint result = null;
            int from = top.getPosition();
            int to = bottom.getPosition();
            int dif = to - from;
            from = (int)(from + dif * safeFromRate);
            to = (int)(from + dif * safeToRate);
            Boolean increase = false;
            for (int j = from; j < to; j++)
            {
                // the position that derivative begin to increase
                if (derivative(pulseDataArray, j) > derivative(pulseDataArray, j - 1))
                {
                    increase = true;
                }
                if (increase)
                {
                    Boolean max = true;
                    for (int k = j - 15; k < j + 15; k++)
                    {
                        if (derivative(pulseDataArray, k) > derivative(pulseDataArray, j))
                            max = false;
                    }
                    if (max)
                    {
                        result = new SpecialPoint(j, SpecialPoint.TYPE_DTOP);
                        int pointLocation = specialPoints.IndexOf(bottom);
                        specialPoints.Insert(pointLocation, result);
                        break;
                    }
                }

            }
            return result;
        }

        //find and mark diastolic top in special points.
        private void markDtops()
        {
            if (specialPoints == null || specialPoints.Count == 0)
            {
                markPoints();
            }
            if (specialPoints.Count > 0)
            {
                for (int i = 0; i < specialPoints.Count; i++)
                {
                    //check if it's a complete wave form
                    if (((SpecialPoint)specialPoints[i]).getType() == SpecialPoint.TYPE_BOTTOM)
                    {
                        Boolean tBool = false;  //check if there's a top following
                        Boolean bBool = false;  //check if there's a bottom following
                        int tPosition = 0;
                        int bPosition = 0;
                        for (int j = i + 1; j < specialPoints.Count; j++)
                        {
                            if (((SpecialPoint)specialPoints[j]).getType() == SpecialPoint.TYPE_TOP)
                            {
                                tBool = true;
                                tPosition = j;
                            }
                            if (((SpecialPoint)specialPoints[j]).getType() == SpecialPoint.TYPE_BOTTOM)
                            {
                                bBool = true;
                                bPosition = j;
                                break;
                            }
                        }
                        if (tBool && bBool)// complete wave form
                        {
                            int sTop = ((SpecialPoint)specialPoints[tPosition]).getPosition();   //systolic 
                            int nextBottom = ((SpecialPoint)specialPoints[bPosition]).getPosition(); //bottom
                            int aBottom = ((SpecialPoint)specialPoints[i]).getPosition();    // position of 1st bottom
                            int length = nextBottom - aBottom;      //length of the wave form
                            int range = nextBottom - sTop;  // points between Systolic and bottom
                            int dTop = sTop + range * 2 / 5;    //default position for diastolic
                            int from = (int)(sTop + range * safeFromRate); // start point
                            int to = (int)(sTop + range * safeToRate);      // end point
                            int dif = bPosition - tPosition;    //# of special points between sTop and nextBottom
                            SpecialPoint aPoint = null;
                            switch (dif)  // different cases to find diastolic
                            {
                                case 1: //no diastolic top found find the first top for derivative


                                    aPoint = findDtopByDerivitive(((SpecialPoint)specialPoints[tPosition]), ((SpecialPoint)specialPoints[bPosition]));
                                    if (aPoint != null) dTop = aPoint.getPosition();

                                    break;
                                case 2: //one little special point found
                                    //check if the point is too far from stop (make si too small, seems to be wrong)
                                    if (((SpecialPoint)specialPoints[tPosition + 1]).getPosition() - sTop > (nextBottom - sTop) * 4 / 7)
                                    {
                                        aPoint = findDtopByDerivitive(((SpecialPoint)specialPoints[tPosition]), ((SpecialPoint)specialPoints[bPosition]));
                                        if (aPoint != null) dTop = aPoint.getPosition();
                                    }
                                    else
                                    {
                                        dTop = ((SpecialPoint)specialPoints[tPosition + 1]).getPosition();
                                        ((SpecialPoint)specialPoints[tPosition + 1]).setType(SpecialPoint.TYPE_DTOP);
                                    }

                                    break;
                                case 3: //normal case
                                    dTop = ((SpecialPoint)specialPoints[tPosition + 2]).getPosition();
                                    ((SpecialPoint)specialPoints[tPosition + 2]).setType(SpecialPoint.TYPE_DTOP);


                                    break;
                                default:    // more special position, find the one nearest to 2/5
                                    int result = 0;
                                    int index = 0;
                                    for (int j = tPosition; j < bPosition; j++)
                                    {
                                        if (System.Math.Abs(((SpecialPoint)specialPoints[j]).getPosition() - dTop) < System.Math.Abs(result - dTop)
                                            && ((SpecialPoint)specialPoints[j]).getType() == SpecialPoint.TYPE_HIGH) //distant to dTop, need to improve
                                        {
                                            result = ((SpecialPoint)specialPoints[j]).getPosition();
                                            index = j;
                                        }
                                    }

                                    if (result - sTop > (nextBottom - sTop) * 4 / 7)
                                    {
                                        aPoint = findDtopByDerivitive(((SpecialPoint)specialPoints[tPosition]), ((SpecialPoint)specialPoints[bPosition]));
                                        if (aPoint != null) dTop = aPoint.getPosition();
                                    }
                                    else
                                    {
                                        dTop = result;
                                        ((SpecialPoint)specialPoints[index]).setType(SpecialPoint.TYPE_DTOP);
                                    }

                                    break;
                            }

                        }
                    }
                }
            }
        }

        public void findPoints()                   // find the special points, mark them and add to points arraylist
        {
            specialPoints.Clear();  //empty the Array for special points
            markPoints();           //find special points
            markDtops();            //find diastolic top
        }



        public void getSIs() //check the special points to find SI, store them to SIs ArrayList
        {
            if (specialPoints == null || specialPoints.Count == 0) this.findPoints();
            SIs.Clear();
            standardSIs.Clear();
            for (int i = 0; i < specialPoints.Count; i++)
            {
                //check if it's a complete wave form
                if (((SpecialPoint)specialPoints[i]).getType() == SpecialPoint.TYPE_BOTTOM)
                {
                    Boolean tBool = false;  //check if there's a top following
                    Boolean dBool = false;  //check if there's a dtop
                    Boolean bBool = false;  //check if there's a bottom following
                    int tPosition = 0;
                    int dPosition = 0;
                    int bPosition = 0;
                    for (int j = i + 1; j < specialPoints.Count; j++)
                    {
                        if (((SpecialPoint)specialPoints[j]).getType() == SpecialPoint.TYPE_TOP)
                        {
                            tBool = true;
                            tPosition = j;
                        }
                        if (((SpecialPoint)specialPoints[j]).getType() == SpecialPoint.TYPE_DTOP)
                        {
                            dBool = true;
                            dPosition = j;
                        }
                        if (((SpecialPoint)specialPoints[j]).getType() == SpecialPoint.TYPE_BOTTOM)
                        {
                            bBool = true;
                            bPosition = j;
                            break;
                        }
                    }
                    if (tBool && dBool && bBool)// complete wave form
                    {
                        int length = ((SpecialPoint)specialPoints[bPosition]).getPosition() - ((SpecialPoint)specialPoints[i]).getPosition();
                        double si = height * 2.0 / (((SpecialPoint)specialPoints[dPosition]).getPosition() - ((SpecialPoint)specialPoints[tPosition]).getPosition());
                        if (si > 3 && si < 20) SIs.Add(si);
                        si = si * length / standardPoints;  //conver to standard pulse rate
                        if (si > 3 && si < 20) standardSIs.Add(si);
                    }
                }
            }
        }

        //calculate the average SI
        public double SIAVG()
        {
            double result = 0;
            int count = 0;

            if (SIs != null && SIs.Count > 0)
            {
                foreach (double si in SIs)
                {
                    result += si;
                    count++;
                }
                result = result / count;
            }
            return result;
        }

        //calculate the average SI for standard pulse rate
        public double standardSIAVG()
        {
            double result = 0;
            int count = 0;

            if (standardSIs != null && standardSIs.Count > 0)
            {
                foreach (double si in standardSIs)
                {
                    result += si;
                    count++;
                }
                result = result / count;
            }
            return result;
        }

        public String specialPointToString()
        {
            String result = "Special Points:\t";
            if (specialPoints.Count > 0)
            {
                for (int i = 0; i < specialPoints.Count; i++)
                {
                    result += ((SpecialPoint)specialPoints[i]).getPosition() + ":" + pulseDataArray[((SpecialPoint)specialPoints[i]).getPosition()] + "(" + ((SpecialPoint)specialPoints[i]).getType() + ");\t";
                }
            }
            else { result += "No Special point"; }
            return result;
        }
        public String SIsToString()
        {
            String result = "SIs:\t";
            if (SIs.Count > 0)
            {
                for (int i = 0; i < SIs.Count; i++)
                {
                    result += SIs[i].ToString() + "\t";
                }
            }
            else { result += "No SI"; }
            return result;
        }

        public string toResult()
        {
            string result = "";
            result += "Heart Rate(HR) is " + getHR() + ", Stiffness Index (SI) is " + standardSIAVG() + ".";
            return result;
        }

        public double getDTWDistance(PulseWave PWData) {
        double result = 0;
        int count = 0;
        //Console.WriteLine("Special Points: " + specialPoints.Count);
        if (this.specialPoints.Count > 1 && PWData.specialPoints.Count > 1) {
            //get wave forms from sample and compare with testing wave forms
            for (int i = 0; i < this.specialPoints.Count-1; i++) {
                if(((SpecialPoint)this.specialPoints[i]).getType() != SpecialPoint.TYPE_BOTTOM) continue;
                int bottomPosition = ((SpecialPoint)this.specialPoints[i]).getPosition();
                if (getNextBottom(bottomPosition + 1) <= bottomPosition) break;
                TimeSeries sample = new TimeSeries(1);
                int count1 = 0;
                
                for (int j = bottomPosition ; j < getNextBottom(bottomPosition+1); j++) {
                    double[] sampleData = { double.Parse(this.pulseDataArray[j].ToString())};
                    sample.addLast(count1, new TimeSeriesPoint(sampleData));
                    count1++;
                }
                //Console.WriteLine("Read sample data: " + sample.size());
                //process testing data
                for (int k = 0; k < PWData.specialPoints.Count - 1; k++) {
                    if(((SpecialPoint)PWData.specialPoints[k]).getType() != SpecialPoint.TYPE_BOTTOM) continue;
                    int bottomPosition2 = ((SpecialPoint)PWData.specialPoints[k]).getPosition();
                    if (PWData.getNextBottom(bottomPosition2 + 1) <= bottomPosition2) break;
                    TimeSeries testing = new TimeSeries(1);
                    int count2 = 0;
                    
                    for (int l = bottomPosition2;
                            l < PWData.getNextBottom(bottomPosition2+1); l++) {
                        double[] testingData = {double.Parse(PWData.pulseDataArray[l].ToString())};
                        testing.addLast(count2, new TimeSeriesPoint(testingData));
                        count2++;
                    }
                    double distance = DTW.getWarpDistBetween(sample, testing);
                    result+= distance;
                    //Console.WriteLine("DTW distance: " + distance);
                    //if (distance < this.mostLikeValue) this.mostLike = k;
                    count++;
                }
            }
            result = result/count;
            //Console.WriteLine("DTW distance: " + result);
            PWData.DTWDistance = result;
        }
        return result;
    }



    }
}

